package fusion

import (
	"bytes"
	"container/list"
	"errors"
	"sync"
)

type dataBuffer struct {
	buffer     [65536]byte
	rpos, wpos int
}

func (obj *dataBuffer) isReadableEmpty() bool {
	return obj.rpos >= obj.wpos
}
func (obj *dataBuffer) isWritableFull() bool {
	return obj.wpos >= len(obj.buffer)
}
func (obj *dataBuffer) getWritableSize() int {
	return len(obj.buffer) - obj.wpos
}
func (obj *dataBuffer) nextData() []byte {
	if obj.rpos >= obj.wpos {
		panic(errors.New("write buffer underflow"))
	}
	data := obj.buffer[obj.rpos:obj.wpos]
	obj.rpos = obj.wpos
	return data
}
func (obj *dataBuffer) writeData(data []byte) {
	if obj.wpos+len(data) > len(obj.buffer) {
		panic(errors.New("write buffer overflow"))
	}
	copy(obj.buffer[obj.wpos:], data)
	obj.wpos += len(data)
}
func (obj *dataBuffer) reset() {
	obj.rpos, obj.wpos = 0, 0
}

type sendBuffer struct {
	outBuffer *dataBuffer
	inBuffer  *dataBuffer
	inBuffers list.List
	mutex     sync.Mutex
}

func (obj *sendBuffer) writePacket(pck *NetPacket) {
	obj.mutex.Lock()
	defer obj.mutex.Unlock()
	obj.writePacketHeader(pck.Opcode, pck.GetReadableSize())
	obj.writeData(pck.GetReadableBytes())
}
func (obj *sendBuffer) writePacketWithData(pck *NetPacket, data []byte) {
	obj.mutex.Lock()
	defer obj.mutex.Unlock()
	obj.writePacketHeader(pck.Opcode, pck.GetReadableSize()+len(data))
	obj.writeData(pck.GetReadableBytes())
	obj.writeData(data)
}
func (obj *sendBuffer) writePacketWithPkt(pck *NetPacket, pkt *NetPacket) {
	obj.mutex.Lock()
	defer obj.mutex.Unlock()
	obj.writePacketHeader(pck.Opcode,
		pck.GetReadableSize()+PacketHeaderSize+pkt.GetReadableSize())
	obj.writeData(pck.GetReadableBytes())
	obj.writePacketHeader(pkt.Opcode, pkt.GetReadableSize())
	obj.writeData(pkt.GetReadableBytes())
}

func (obj *sendBuffer) writePacketHeader(opcode int, length int) {
	var buffer bytes.Buffer
	WriteNetPacketHeader(&buffer, opcode, length)
	obj.writeData(buffer.Bytes())
}

func (obj *sendBuffer) nextData() []byte {
	if obj.outBuffer != nil && !obj.outBuffer.isReadableEmpty() {
		return obj.outBuffer.nextData()
	}
	obj.swapBuffer()
	if obj.outBuffer != nil && !obj.outBuffer.isReadableEmpty() {
		return obj.outBuffer.nextData()
	}
	return nil
}

func (obj *sendBuffer) writeData(data []byte) {
	for len(data) > 0 {
		if obj.inBuffer != nil && obj.inBuffer.isWritableFull() {
			obj.inBuffers.PushBack(obj.inBuffer)
			obj.inBuffer = nil
		}
		if obj.inBuffer == nil {
			obj.inBuffer = new(dataBuffer)
		}
		n := MinInt(obj.inBuffer.getWritableSize(), len(data))
		obj.inBuffer.writeData(data[:n])
		data = data[n:]
	}
}

func (obj *sendBuffer) swapBuffer() {
	obj.mutex.Lock()
	defer obj.mutex.Unlock()
	if obj.inBuffers.Len() == 0 {
		obj.inBuffer, obj.outBuffer = obj.outBuffer, obj.inBuffer
		if obj.inBuffer != nil {
			obj.inBuffer.reset()
		}
	} else {
		obj.outBuffer = obj.inBuffers.Remove(obj.inBuffers.Front()).(*dataBuffer)
	}
}

func (obj *sendBuffer) hasData() bool {
	obj.mutex.Lock()
	defer obj.mutex.Unlock()
	return (obj.outBuffer != nil && !obj.outBuffer.isReadableEmpty()) ||
		(obj.inBuffer != nil && !obj.inBuffer.isReadableEmpty()) ||
		obj.inBuffers.Len() != 0
}
